/*Copyright (C) 2011  Jens Schwarzhaupt

This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.

This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
GNU General Public License for more details.

You should have received a copy of the GNU General Public License
along with this program.  If not, see <http://www.gnu.org/licenses/>.*/

package de.dhbw.GpsTools;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Date;
import java.util.List;

/**This class represents a single gps-track with some additional methods.
 * 
 * A gps-track keeps multiple ordered gps-waypoints which represent a route.  
 * @author Jens Schwarzhaupt
 *
 */
public class AdvancedTrack extends Track{
	
	private static final long serialVersionUID = 1L;
	private List<Double> distances = new ArrayList<Double>();
	
	/**Add a new Waypoint object to the gps-track
	 * 
	 * @param wpt The Waypoint object.
	 * @return true.
	 */
	public boolean add(Waypoint wpt){
		GpsCalculator calc = new GpsCalculator();
		if (this.size() > 0){
			distances.add(calc.getDistance(this.get(this.size()-1), wpt));
			super.add(wpt);
			return true;
		}
		else{
			distances.add(0.0);
			super.add(wpt);
			return true;
		}
	}
	
	/**Add a new Waypoint object to the track at the position specified by index.
	 * 
	 * @param index The position the new Waypoint object will be added to.
	 * @param wpt The Waypoint object.
	 */
	public void add(int index, Waypoint wpt){
		GpsCalculator calc = new GpsCalculator();
		
		if (index > 0){
			super.add(index, wpt);
			distances.add(index,calc.getDistance(this.get(index-1), wpt));
			distances.set(index+1, calc.getDistance(wpt, this.get(index+1)));
		}
		else if (index == 0){
			super.add(index,wpt);
			distances.add(index,0.0);
			distances.set(index+1, calc.getDistance(wpt, this.get(index+1)));
		}
	}
	
	/**Add a list of Waypoint objects to the track.
	 * 
	 * @param wpts A list of Waypoint objects.
	 * @return true.
	 */
	public boolean addAll(ArrayList<? extends Waypoint> wpts){
		for (int i = 0; i < wpts.size(); i++){
			this.add(wpts.get(i));
		}
		return true;
	}
	
	/**Add a list of Waypoint objects to the track at the position specified by index.
	 * 
	 * @param index The position the new Waypoint objects will be added to.
	 * @param wpts A list of Waypoint objects.
	 * @return true.
	 */
	public boolean addAll(int index, ArrayList<? extends Waypoint> wpts){
		GpsCalculator calc = new GpsCalculator();
		for (int i = 0; i < wpts.size(); i++){
			super.add(index+i, wpts.get(i));
			if (index + i> 0){
				
				distances.add(index+i,calc.getDistance(this.get(index+i-1), wpts.get(i)));
			}
			else if (index + i == 0){
				distances.add(index,0.0);
			}
		}
		distances.set(index+wpts.size(), calc.getDistance(wpts.get(wpts.size()-1), this.get(index+wpts.size())));
		
		return true;
	}
	
	/**Remove all Waypoint objects
	 * 
	 */
	public void clear(){
		super.clear();
		distances.clear();
	}
	
	/**Remove a Waypoint object from the track specified by index.
	 * 
	 */
	public Waypoint remove(int index){
		GpsCalculator calc = new GpsCalculator();
		distances.remove(index);
		if (index == 0){
			distances.set(index, 0.0);
		}
		else{
			distances.set(index, calc.getDistance(this.get(index-1), this.get(index+1)));
		}
		return super.remove(index);
	}
	
	/**Search the track for a specific Waypoint object and remove it from the track.
	 * 
	 * @param wpt The Waypoint object that will be deleted.
	 * @return true.
	 */
	public boolean remove(Waypoint wpt){
		int index = this.indexOf(wpt);
		this.remove(index);
		return true;
	}
	
	/**Note: THIS METHOD IS NOT SUPPORTED YET!
	 * 
	 */
	public boolean removeAll(Collection wpts){
		throw new UnsupportedOperationException("This Method is not supported in AdvancedTrack-Class");
	}
	
	/**Note: THIS METHOD IS NOT SUPPORTED YET!
	 * 
	 */
	public boolean retainAll(Collection wpts){
		throw new UnsupportedOperationException("This Method is not supported in AdvancedTrack-Class");
	}
	
	/**Add a new Waypoint object to the Track at the position specified by index.
	 * The previous Waypoint object at this position will be deleted.
	 * 
	 * @param index The position the new Waypoint object will be added to.
	 * @param wpt The Waypoint object.
	 * @return The previous Waypoint object at this position.
	 */
	public Waypoint set(int index, Waypoint wpt){
		GpsCalculator calc = new GpsCalculator();
		if (index > 0){
			distances.set(index, calc.getDistance(this.get(index-1), wpt));
			distances.set(index+1, calc.getDistance(this.get(index), wpt));
		}
		else if (index == 0){
			distances.set(index, 0.0);
			distances.set(index+1, calc.getDistance(this.get(index), wpt));
		}
		return super.set(index, wpt);
	}
	
	/**Get the overall distance of the gps-track.
	 * 
	 * @return Overall distance in meters.
	 */
	public double getOverallDistance(){
		double distance = 0;
		for (int i = 0; i < distances.size(); i++){
			distance += distances.get(i);
		}
		return distance;
	}
	
	/**Get the overall duration of the track.
	 * Note: This method only works correctly if every Waypoint object
	 * has a timestamp and all Waypoint objects are ordered chronologically.
	 * 
	 * @return The overall duration in milliseconds.
	 */
	public long getOverallDuration(){
		return this.getEndTime().getTime() - this.getStartTime().getTime();
	}
	
	/**Get the start time of the track
	 * Note: This method only works correctly if every Waypoint object
	 * has a timestamp and all Waypoint objects are ordered chronologically.
	 * 
	 * @return The start time as java.util.Date object.
	 */
	public Date getStartTime(){
		return this.get(0).getTime();
	}
	
	/**Get the end time of the track
	 * Note: This method only works correctly if every Waypoint object
	 * has a timestamp and all Waypoint objects are ordered chronologically.
	 * 
	 * @return The end time as java.util.Date object.
	 */
	public Date getEndTime(){
		return this.get(this.size()-1).getTime();
	}

	
	/**Get a list of all distances between two nearby waypoints.
	 * 
	 * At the first index the distance is always 0. At the second index
	 * the distance between the first and the second waypoint is stored 
	 * and so on...
	 * The items are stored in the same order as the Waypoint object in the track.
	 * 
	 * @return A list with all distances.
	 */
	public List<Double> getDistances(){
		return distances;
	}

	/**Get a list of all elevations.
	 * The items are stored in the same order as the Waypoint object in the track.
	 * 
	 * @return A list with all elevations.
	 */
	public List<Double> getElevations(){
		List<Double> elevations = new ArrayList<Double>();
		for (int i = 0; i < this.size(); i++){
			elevations.add(this.get(i).getElevation());
		}
		return elevations;
	}
	
	/**Get a list of all durations between two nearby waypoints
	 * 
	 * At the first index the duration is always 0. At the second index
	 * the duration between the first and the second waypoint is stored 
	 * and so on...
	 * The items are stored in the same order as the Waypoint object in the track.
	 * 
	 * @return A list with all durations.
	 */
	
	public double getPositiveElevation()
	{
		double deltaEle = 0;
		List<Double> eleList = getElevations();
		for(int i = 1; i < eleList.size(); i++)
		{
			if (eleList.get(i) - eleList.get(i-1) > 0.1)
			{
				deltaEle += (eleList.get(i) - eleList.get(i-1));
			}
		}
		
		return deltaEle;
	}
	
	public List<Long> getDurations(){
		GpsCalculator calc = new GpsCalculator();
		List<Long> durations = new ArrayList<Long>();
		durations.add((long) 0);
		for (int i = 1; i < this.size(); i++){
			durations.add(calc.getDuration(this.get(i), this.get(i-1)));
		}
		return durations;
	}
	
	/**Get a list of all timestamps.
	 * The items are stored in the same order as the Waypoint object in the track.
	 * 
	 * @return A list with all timestamps.
	 */
	public List<Date> getTimes(){
		List<Date> times = new ArrayList<Date>();
		for (int i = 0; i < this.size(); i++){
			times.add(this.get(i).getTime());
		}
		return times;
	}
	
	/**Get a list with the satfix quality of every Waypoint object.
	 * The items are stored in the same order as the Waypoint object in the track.
	 * 
	 * @return A list with the satfix quality of every Waypoint object.
	 */
	public List<Integer> getFixQualities(){
		List<Integer> fix = new ArrayList<Integer>();
		for (int i = 0; i < this.size(); i++){
			fix.add(this.get(i).getSatFixQuality());
		}
		return fix;
	}
	
	/**Get a list with the number of satellites of every Waypoint object.
	 * The items are stored in the same order as the Waypoint object in the track.
	 * 
	 * @return A list with the number of satellites of every Waypoint object.
	 */
	public List<Integer> getNumbersOfSats(){
		List<Integer> sats = new ArrayList<Integer>();
		for (int i = 0; i < this.size(); i++){
			sats.add(this.get(i).getNumberOfSats());
		}
		return sats;
	}
	
	/**Get a list of all longitudes.
	 * The items are stored in the same order as the Waypoint object in the track.
	 * 
	 * @return A list with all longitudes.
	 */
	public List<Double> getLongitudes(){
		List<Double> lons = new ArrayList<Double>();
		for (int i = 0; i < this.size(); i++){
			lons.add(this.get(i).getLongitude());
		}
		return lons;
	}
	
	/**Get a list of all latitudes.
	 * The items are stored in the same order as the Waypoint object in the track.
	 * 
	 * @return A list with all latitudes.
	 */
	public List<Double> getLatitudes(){
		List<Double> lats = new ArrayList<Double>();
		for (int i = 0; i < this.size(); i++){
			lats.add(this.get(i).getLatitude());
		}
		return lats;
	}
	
	/**Get a list of all courses / directions.
	 * The items are stored in the same order as the Waypoint object in the track.
	 * 
	 * @return A list with all courses.
	 */
	public List<Double> getCourses(){
		List<Double> courses = new ArrayList<Double>();
		for (int i = 0; i < this.size(); i++){
			courses.add(this.get(i).getCourse());
		}
		return courses;
	}
	
	/**Get a list of all speeds.
	 * The items are stored in the same order as the Waypoint object in the track.
	 * 
	 * @return A list with all speeds.
	 */
	public List<Double> getSpeeds(){
		List<Double> speeds = new ArrayList<Double>();
		for (int i = 0; i < this.size(); i++){
			speeds.add(this.get(i).getSpeed());
		}
		return speeds;
	}
	
}

